from abc import ABC, abstractmethod
from typing import Any, Dict, List

from ctaf_core.ctaf_factory import CtafFactory
from ctaf_core.ctaf_objects.base_data_objects import (
        BaseEventData,
        TickEventData,
        TradeEventData,
        OrderEventData,
        PositionEventData,
        AccountEventData,
        QuoteEventData,
        ContractEventData,
        LogEventData
)
from ctaf_core.ctaf_objects.base_event import MsgEvent
from ctaf_core.ctaf_objects.ctaf_outbound_request import SubscribeRequest, OrderRequest, CancelRequest, QuoteRequest
from ctaf_core.ctaf_resources.ctaf_settings import CTAFsettings
from ctaf_core.ctaf_utils.config_process_util import load_gateway_settings
from ctaf_facade.enumerations.event_type_enum import EventTypeEnum
from ctaf_facade.enumerations.exchange_enum import ExchangeEnum, get_exchange_enum_by_value
from ctaf_facade.enumerations.msg_topic_enum import MsgTopicEnum


class BaseBizApp(ABC):
    """
    Abstract class of a business application in ctaf_core.
    """

    def __init__(self, ctaf: Any = None, app_name: str = None):

        if app_name:
            self.__app_name = app_name
        else:
            self.__app_name =type(self).__name__

        if ctaf:
            self.ctaf = ctaf
        else:
            self.ctaf = CtafFactory.get_ctaf_instance()


    def get_app_name(self)-> str:
        return self.__app_name


class BaseGatewayApp(BaseBizApp):
    """
    Abstract gateway app class for creating gateways connection to different trading systems.
    """

    def __init__(self, ctaf: Any, gateway_app_name: str) -> None:
        super().__init__(ctaf, gateway_app_name)
        # 合约数据全局缓存字典
        self.symbol_contract_map: dict[str, ContractEventData] = {}
        self.gateway_setting: Dict = self.get_gateway_setting()
        print(self.gateway_setting)
        self.connect_info: Dict[str, Any] = self.gateway_setting["connectInfo"]
        self.exchanges_supported: List[ExchangeEnum] = self.get_exchanges_supported()

    def get_gateway_setting(self)-> Dict[str, Any]:
        gateway_setting_filename: str = CTAFsettings.get_setting("gatewaySettingFilename")
        gateway_settings = load_gateway_settings(gateway_setting_filename)
        if gateway_settings:
            return gateway_settings[self.get_app_name()]

    def get_exchanges_supported(self)-> List[ExchangeEnum]:
        exchange_values: List = self.gateway_setting["exchangesSupported"]
        exchanges: List[ExchangeEnum] = []
        for value in exchange_values:
            exchange = get_exchange_enum_by_value(value)
            if not exchange:
                exchanges.append(exchange)
        return exchanges

    def on_event(self, event_type: EventTypeEnum, event_data: BaseEventData, msg_topic: MsgTopicEnum = MsgTopicEnum.DEFAULT_TOPIC) -> None:
        msg_event: MsgEvent = MsgEvent(event_type, event_data, msg_topic)
        self.ctaf.pub_event(msg_event)

    def on_tick(self, tick: TickEventData) -> None:

        self.on_event(EventTypeEnum.EVENT_TICK, tick)

    def on_trade(self, trade: TradeEventData) -> None:

        self.on_event(EventTypeEnum.EVENT_TRADE, trade)

    def on_order(self, order: OrderEventData) -> None:

        self.on_event(EventTypeEnum.EVENT_ORDER, order)

    def on_position(self, position: PositionEventData) -> None:

        self.on_event(EventTypeEnum.EVENT_POSITION, position)

    def on_account(self, account: AccountEventData) -> None:

        self.on_event(EventTypeEnum.EVENT_ACCOUNT, account)

    def on_quote(self, quote: QuoteEventData) -> None:

        self.on_event(EventTypeEnum.EVENT_QUOTE, quote)


    def on_contract(self, contract: ContractEventData) -> None:

        self.on_event(EventTypeEnum.EVENT_CONTRACT, contract)

    def write_log(self, msg: str) -> None:
        format_log_msg = f"Gateway {self.get_app_name()} : {msg}"
        self.ctaf.log_info(format_log_msg)
        log_event_data: LogEventData = LogEventData(format_log_msg)
        self.on_event(EventTypeEnum.EVENT_LOG,log_event_data)

    def write_error(self, msg: str, error: dict) -> None:
        """输出错误信息日志"""
        error_id: int = error["ErrorID"]
        error_msg: str = error["ErrorMsg"]
        msg: str = f"{msg}，代码：{error_id}，信息：{error_msg}"
        self.write_log(msg)


    @abstractmethod
    def connect(self) -> None:
        """
        Start gateway connection.

        to implement this method, you must:
        * connect to server if necessary
        * log connected if all necessary connection is established
        * do the following query and response corresponding on_xxxx and write_log
            * contracts : on_contract
            * account asset : on_account
            * account holding: on_position
            * orders of account: on_order
            * trades of account: on_trade
        * if any of query above is failed,  write log.

        future plan:
        response callback/change status instead of write_log

        """
        pass

    @abstractmethod
    def close(self) -> None:
        """
        Close gateway connection.
        """
        pass

    @abstractmethod
    def subscribe(self, req: SubscribeRequest) -> None:
        """
        Subscribe tick data update.
        """
        pass

    @abstractmethod
    def send_order(self, req: OrderRequest) -> str:
        """
        Send a new order to server.

        implementation should finish the tasks blow:
        * create an OrderData from req using OrderRequest.create_order_data
        * assign a unique(gateway instance scope) id to OrderData.orderid
        * send request to server
            * if request is sent, OrderData.status should be set to Status.SUBMITTING
            * if request is failed to sent, OrderData.status should be set to Status.REJECTED
        * response on_order:
        * return vt_orderid

        :return str vt_orderid for created OrderData
        """
        pass

    @abstractmethod
    def cancel_order(self, req: CancelRequest) -> None:
        """
        Cancel an existing order.
        implementation should finish the tasks blow:
        * send request to server
        """
        pass

    def send_quote(self, req: QuoteRequest) -> str:
        """
        Send a new two-sided quote to server.

        implementation should finish the tasks blow:
        * create an QuoteData from req using QuoteRequest.create_quote_data
        * assign a unique(gateway instance scope) id to QuoteData.quoteid
        * send request to server
            * if request is sent, QuoteData.status should be set to Status.SUBMITTING
            * if request is failed to sent, QuoteData.status should be set to Status.REJECTED
        * response on_quote:
        * return vt_quoteid

        :return str vt_quoteid for created QuoteData
        """
        return ""

    def cancel_quote(self, req: CancelRequest) -> None:
        """
        Cancel an existing quote.
        implementation should finish the tasks blow:
        * send request to server
        """
        pass

    @abstractmethod
    def query_account(self) -> None:
        """
        Query account balance.
        """
        pass

    @abstractmethod
    def query_position(self) -> None:
        """
        Query holding positions.
        """
        pass

